#include <stdlib.h>
#include <unistd.h>
#include <stddef.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <string.h>
#include <getopt.h>
#include <errno.h>
#include <signal.h>
#include <ctype.h>
#include <dirent.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <linux/fb.h>

/*********************
 *      DEFINES
 *********************/
#ifndef FBDEV_PATH
#define FBDEV_PATH  "/dev/fb0"
#endif

/**********************
 *      TYPEDEFS
 **********************/

/**********************
 *      STRUCTURES
 **********************/

/**********************
 *  STATIC PROTOTYPES
 **********************/

/**********************
 *  STATIC VARIABLES
 **********************/
static struct fb_var_screeninfo vinfo;
static struct fb_fix_screeninfo finfo;

static char *fbp = 0;
static long int screensize = 0;
static int fbfd = 0;

static unsigned long int s_buff_index = 0;
static int fb_nr = 0;

static char *pc_prog_name = NULL;   
static int test_flag = 0;

/**********************
 *      MACROS
 **********************/
#define LEN_HINT                512
#define LEN_OPTION_SHORT        512

#define SCREEN_HEIGHT (480)
#define SCREEN_WIDTH (800)

#define FBIOGET_CUS_LCDBUSY (0x4680)

/**********************
 *   GLOBAL FUNCTIONS
 **********************/

static char ac_option_hint[  ][ LEN_HINT ] = {    //操作提示数组
    "打印帮助信息" ,
    " ",
};

static struct option option_long[ ] = {
    { "help"       , no_argument       , NULL , 'h' } ,      //"打印帮助信息" ,
    { "test"       , required_argument , NULL , 't' } ,      //
    { 0                   , 0                 , 0    , 0   } ,
 };

/*
 * help_hint: use the -h --help option.Print option of help information
 * return: 0
 */
static void usage(const char * name)
{
    printf("\nexample: \r\n"
            "\t%s -t \n", name);
}

static int help_hint(void)
{
    int i;

    printf( "%s\n" , pc_prog_name);

    /* parse the all supported option */
    printf( "options: \r\n");
    for(i = 0; i < sizeof(option_long) / sizeof(struct option); i ++) {
        if( option_long[ i ].val != 0 )
            printf("\t-%c, --%-16s\t%s\n" , option_long[ i ].val , option_long[ i ].name , ac_option_hint[ i ]);
    }

    usage(pc_prog_name);
    printf("\n\n");
    return 0;
}

/*
 * get_option_short: fill the stort option string.
 * return: option short string addr.
 */
static char *get_option_short( struct option *p_option, int i_num_option, char *pc_option_short, int i_len_option )
{
    int i;
    int i_offset = 0;
    char c_option;

    for( i = 0 ; i < i_num_option ; i ++ )
    {
        if( ( c_option = p_option[ i ].val ) == 0 )
            continue;
        
        switch( p_option[ i ].has_arg )
        {
        case no_argument:
            i_offset += snprintf( pc_option_short + i_offset , i_len_option - i_offset , "%c" , c_option );
            break;
        case required_argument:
            i_offset += snprintf( pc_option_short + i_offset , i_len_option - i_offset , "%c:" , c_option );
            break;
        case optional_argument:
            i_offset += snprintf( pc_option_short + i_offset , i_len_option - i_offset , "%c::" , c_option );
            break;
        }
    }
    return pc_option_short;
}

static int parse_option( int argc, char **argv )
{
    int i_option;

    char ac_option_short[ LEN_OPTION_SHORT ];
    int i_array_num = sizeof( option_long ) / sizeof( struct option ) ;
    char c_flag = 0;
    pc_prog_name = argv[ 0 ];

    optind = 0;
    get_option_short( option_long, i_array_num , ac_option_short , LEN_OPTION_SHORT );
    while((i_option = getopt_long(argc , argv , ac_option_short , option_long , NULL)) > 0) {
        switch(i_option) {
            case 'h' :                                                          //help
                help_hint();
                c_flag = -1;
                goto parse_option_end;
            case 't' :                                                          //screen type 
                test_flag = atoi( optarg );
                break;
            default :
                help_hint();
                c_flag = -1;
                goto parse_option_end;
                
        }
    }
parse_option_end:

    return c_flag;
}

void lcd_check_busy(int fd)
{
    int ret = 0;
    unsigned long stat = 1;
      
    while(stat)
    {
        ret = ioctl(fd, FBIOGET_CUS_LCDBUSY, &stat);
        if(ret < 0)
        {
            perror("FBIOGET_CUS_LCDBUSY fail\n");
            return;
        }

        if(stat == 1)
        {
             printf("FBIOGET_CUS_LCDBUSY busy: %ld\n", stat);
             usleep(15000);
        }
    }
}

int main(int argc, char const *argv[])
{
    unsigned char* buff = NULL;
    int ret = 0;   

	/* parse the option */
    if( parse_option( argc, ( char **)argv ) == -1 )            //解释和配置选项
    {
        return -1;                                   //打印帮助后退出
    }

	// Open the file for reading and writing
    fbfd = open(FBDEV_PATH, O_RDWR);
    if(fbfd == -1) {
        perror("Error: cannot open framebuffer device");
        return -1;
    }
    printf("The framebuffer device was opened successfully.\n");

    // Get fixed screen information
    if(ioctl(fbfd, FBIOGET_FSCREENINFO, &finfo) == -1) {
		perror("Error reading fixed information");
		goto exit;
    }

    // Get variable screen information
    if(ioctl(fbfd, FBIOGET_VSCREENINFO, &vinfo) == -1) {
        perror("Error reading variable information");
        goto exit;
    }

    fb_nr = (finfo.reserved[0] == 0)?1:2;
    printf("Current fb number is %d\n", fb_nr ); 

    printf("vinfo.xres:%d\n", vinfo.xres);
    printf("vinfo.yres:%d\n", vinfo.yres);
    printf("vinfo.xres_virtual:%d\n", vinfo.xres_virtual);
    printf("vinfo.yres_virtual:%d\n", vinfo.yres_virtual);
	printf("vinfo.yoffset:%d\n", vinfo.yoffset);
	printf("vinfo.yoffset:%d\n", vinfo.yoffset);
	printf("finfo.line_length:%d\n", finfo.line_length);
	printf("%dx%d, %dbpp\n", vinfo.xres, vinfo.yres, vinfo.bits_per_pixel);
	printf("finfo.smem_len:%d\n", finfo.smem_len);
    printf("taotal buffer len:%d\n", vinfo.xres * vinfo.yres * vinfo.bits_per_pixel / 8 * fb_nr);

    if( vinfo.xres != vinfo.xres_virtual || vinfo.yres_virtual != vinfo.yres)
    {
        vinfo.activate = FB_ACTIVATE_NOW | FB_ACTIVATE_FORCE;
        vinfo.xres = vinfo.xres_virtual;
        vinfo.yres = vinfo.yres_virtual;
        if (ioctl(fbfd, FBIOPUT_VSCREENINFO, &(vinfo)) < 0) {
            perror("Update anyka buffer format fail.");
            goto exit;
        }

#if 0
        // Get variable screen information
        if(ioctl(fbfd, FBIOGET_VSCREENINFO, &vinfo) == -1) {
            perror("Error reading variable information");
            goto exit;
        }

        printf("New vinfo.xres_virtual:%d\n", vinfo.xres_virtual);
        printf("New vinfo.yres_virtual:%d\n", vinfo.yres_virtual);
        printf("New vinfo.yoffset:%d\n", vinfo.yoffset);
        printf("New vinfo.yoffset:%d\n", vinfo.yoffset);
#endif
    }

    // Figure out the size of the screen in bytes
    screensize =  finfo.smem_len; //finfo.line_length * vinfo.yres;    

    // Map the device to memory
    fbp = (char *) mmap(0, screensize, PROT_READ | PROT_WRITE, MAP_SHARED, fbfd, 0);
    if((intptr_t) fbp == -1) {
        perror("Error: failed to map framebuffer device to memory");
        goto exit;
    }
    printf("The framebuffer device was mapped to memory successfully.\n");
    printf("fbp addr:%p\n", fbp);

	/* 
	*  初始化第一个可写的buffer索引，
	*/
    s_buff_index = (vinfo.reserved[ 0 ] == 0)?0:1;
   //s_buff_index = (vinfo.reserved[ 0 ] == 0)?1:0;

    printf("Init buffer index:%d\n", s_buff_index);    

	/* Draw pixel */
    while(1)
    {
        if(fb_nr == 2)  /* 判断为双buffer*/
        {
            s_buff_index++;
            if (s_buff_index >= fb_nr) {
                s_buff_index = 0;
            }
            buff = fbp + (vinfo.yres * finfo.line_length) * s_buff_index;

            printf("Current draw buffer addr:%p\n", buff);

            if(s_buff_index == 0)
                memset(fbp, 0xff, screensize);
            else
                memset(fbp, 0, screensize);

            lcd_check_busy(fbfd);

            vinfo.reserved[ 0 ] = ( vinfo.reserved[ 0 ] + 1 ) % 2 ;
            int i_ret = ioctl( fbfd, FBIOPUT_VSCREENINFO, &(vinfo)) ;
            if(i_ret < 0)
            {
                printf("Change buffer pointer fail\n"); 
            } 
        }
        else
        {
            if(s_buff_index == 0)
			{
				memset(fbp, 0xff, screensize);
            	s_buff_index++;
			}
            else
			{
                memset(fbp, 0, screensize);
				s_buff_index = 0;
			}

            /* MPU LCD Must send flush command */
            if( vinfo.xres < 480 || vinfo.yres < 480)
            {
                vinfo.reserved[ 0 ] = ( vinfo.reserved[ 0 ] + 1 ) % 2 ;
                int i_ret = ioctl( fbfd, FBIOPUT_VSCREENINFO, &(vinfo)) ;
                if(i_ret < 0)
                {
                    printf("Change buffer pointer fail\n"); 
                } 
            }
        }

        sleep(1);
    }

exit:
	if(fbfd != -1)
	{
		close(fbfd);
		fbfd = -1;
	}

	s_buff_index = 0;
	fb_nr = 0;

	return 0;
}
